<?php


namespace App\Service\baofu\SecurityUtil;
/**
 * @Desc:RSA加解密
 * @author dasheng@baofu.com(大圣)
 * @datetime 2025/4/5 20:19
 */
class NewRsaUtil
{
    /**
     * 读取私钥方法
     * @param type $PfxPath
     * @param type $PrivateKPASS
     * @return array
     * @throws Exception
     */
    private static function  getPriveKey($PfxPath,$PrivateKPASS){
        if(!file_exists($PfxPath))  throw new \Exception("私钥文件不存在！路径：".$PfxPath);
        $PKCS12 = file_get_contents($PfxPath);
        $PrivateKey = array();
        if(openssl_pkcs12_read($PKCS12, $PrivateKey, $PrivateKPASS)){
            return $PrivateKey["pkey"];
        }else{
            throw new \Exception("私钥证书读取出错！原因[证书或密码不匹配]，请检查本地证书相关信息。");
        }
    }

    /***
     * PEM私钥读取
     * @param $PemPath
     * @return false|string
     * @throws Exception
     */
    private static function  getPemKey($PemPath)
    {
        if(!file_exists($PemPath))  throw new \Exception("PEM私钥文件不存在！路径：".$PemPath);
        $pemkey = file_get_contents($PemPath);
        if(!@openssl_pkey_get_private($pemkey)) throw new Exception("读取PEM私钥失败！路径：".$PemPath);
        return $pemkey;
    }

    /***
     * 公钥读取方法
     * @param $PublicPath
     * @return resource
     * @throws Exception
     */
    private static function  getPublicKey($PublicPath){
        if(!file_exists($PublicPath))  throw new \Exception("公钥文件不存在！路径：".$PublicPath);
        $KeyFile = file_get_contents($PublicPath);
        $PublicKey = openssl_get_publickey($KeyFile);
        if(empty($PublicKey)) throw new \Exception("公钥不可用！路径：".$PublicPath);
        return $PublicKey;
    }

    /***
     * 根据密钥获取分块长度
     * @param $Key
     * @param $Mode true：加密，false:解密
     * @return int
     */
    private static function getBlockSize($Key,$Mode=true)
    {
        try {
            $keyResource = array();
            if(@openssl_pkey_get_private($Key)){
                $keyResource = openssl_pkey_get_private($Key);
            }else if(@openssl_pkey_get_public($Key)){
                $keyResource = openssl_pkey_get_public($Key);
            }else throw new Exception("Is not a key");
            $keyDetails = openssl_pkey_get_details($keyResource);
            if (!$keyDetails || !isset($keyDetails['bits'])) throw new \Exception("无法获取密钥位数");
            $modulusSize = $keyDetails['bits'];
            if($Mode) $blockSize = (int) ($modulusSize/8)-11;
            else $blockSize = (int) ($modulusSize/8);
            return $blockSize;
        } catch (\Exception $ex) {
            return 0;
        }
    }

    /**
     * 获取证书类型
     * @param $key
     * @return string
     */
    private static function getKeyType($key)
    {
        if(@openssl_pkey_get_private($key)){
            return "prikey";
        }else if(@openssl_pkey_get_public($key)){
            return "pubkey";
        }else{
            return "unknow";
        }
    }


    /***
     * 加密核心方法
     * @param $SrcData
     * @param $key
     * @param $Mode
     * @return string|void
     * @throws Exception
     */
    private static function doFinal($SrcData,$key,$Mode=true)
    {
        $Block_Size =   self::getBlockSize($key,$Mode);//分块长度
        $Data_Length = strlen($SrcData);//数据总长度
        if($Block_Size <= 0) throw new \Exception("[Block_Size]分块长度异常:".$Block_Size);
        if($Data_Length <= 0) throw new \Exception("[Data_Length]数据长度小于或等于零");
        if (!$key) throw new \Exception("需要提供密钥，进行加解密操作！");

        $keytype = self::getKeyType($key);//获取证书类型
        if($keytype === "unknow") throw new \Exception("无法识别证书！");
        $CipherRsult="";
        for ($i = 0; $i < $Data_Length; $i += $Block_Size){
            $chunk = substr($SrcData, $i, $Block_Size);//分块加密串
            $cryptedChunk = "";
            try {
                if($Mode){
                    if($keytype === "pubkey"){
                        if(!openssl_public_encrypt($chunk,$cryptedChunk,$key))
                            throw new \Exception("公钥加密异常，在分段：$i");
                    }else{

                        if(!openssl_private_encrypt($chunk,$cryptedChunk,$key))
                            throw new \Exception("私钥加密异常，在分段：$i");
                    }
                }else{
                    if($keytype === "pubkey"){
                        if(!openssl_public_decrypt($chunk,$cryptedChunk,$key))
                            throw new \Exception("公钥解密异常，在分段：$i");
                    }else{
                        if(!openssl_private_decrypt($chunk,$cryptedChunk,$key))
                            throw new \Exception("私钥解密异常，在分段：$i");
                    }
                }
            }catch (Exception $ex){
            }
            $CipherRsult .= $cryptedChunk;
        }
        return $CipherRsult;
    }

    /**
     * 公钥加密
     * @param $SrcData
     * @param $keyPath
     * @return string
     */
    public static function encryptByPub($SrcData,$keyPath)
    {
        $key = self::getPublicKey($keyPath);
        $baseStr = base64_encode($SrcData);
        $enresult = self::doFinal($baseStr,$key);
        return bin2hex($enresult);//输出16进制
    }

    /**
     * 兼容PHP 8.0 的读取密钥方法，需将PFX转为PEM
     * @param $SrcData
     * @param $keyPath
     * @param $pwd
     * @param $keytype 默认PFX,可传PEM
     * @return string
     * @throws Exception
     */
    public static function encryptByPfx($SrcData,$keyPath,$pwd="",$keytype="PFX")
    {
        $key = null;
        if(strtoupper($keytype) === "PFX"){
            $key = self::getPriveKey($keyPath,$pwd);
        }else if(strtoupper($keytype) === "PEM"){
            $key = self::getPemKey($keyPath);
        }
        $baseStr = base64_encode($SrcData);
        $enresult = self::doFinal($baseStr,$key);
        return bin2hex($enresult);//输出16进制
    }


    /***
     * 公钥解密
     * @param $SrcData
     * @param $keyPath
     * @return false|string
     */
    public static function decrptByPub($SrcData,$keyPath)
    {
        $key = self::getPublicKey($keyPath);
        $debin2hex = hex2bin($SrcData);
        $enresult = self::doFinal($debin2hex,$key,false);
        return base64_decode($enresult);
    }

    /**
     * 兼容PHP 8.0 的读取密钥方法，需将PFX转为PEM
     * @param $SrcData
     * @param $keyPath
     * @param $pwd
     * @param $keytype  默认PFX,可传PEM
     * @return false|string
     * @throws Exception
     */
    public static function decrptByPfx($SrcData,$keyPath,$pwd="",$keytype="PFX")
    {
        $key = null;
        if(strtoupper($keytype) === "PFX"){
            $key = self::getPriveKey($keyPath,$pwd);
        }else if(strtoupper($keytype) === "PEM"){
            $key = self::getPemKey($keyPath);
        }
        $debin2hex = hex2bin($SrcData);
        $enresult = self::doFinal($debin2hex,$key,false);
        return base64_decode($enresult);
    }



}
